package sim.test;

import camera.AbstractCamera;
import sim.camera.SimCam;
import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.collision.CollisionResults;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Ray;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.shape.Line;
import java.util.ArrayList;
import java.util.Random;
import matching_algorithms.AbstractMatching;
import matching_algorithms.HungarianMatching;
import sim.system.SystemSetup;
import sim.env.Environment;
import sim.env.Obstacle;
import sim.models.BaseModel;
import sim.models.GolemModel;
import sim.models.ModelFactory;
import sim.models.SinbadModel;

/**
 * Test multiple viewports.
 * 
 * @author Ahmed Abdelkader
 */
public class SimulatorZ extends SimpleApplication {

    private final Vector3f X = Vector3f.UNIT_X;
    private final Vector3f Y = Vector3f.UNIT_Y;
    private final Vector3f Z = Vector3f.UNIT_Z;
    private BulletAppState bulletAppState;
    private ArrayList<BaseModel> models = new ArrayList<BaseModel>();
    private ArrayList<AbstractCamera> cameras = new ArrayList<AbstractCamera>();
    private Node bodies;
    private final float cameraHeight = 160f;
    private final int REFRESH_RATE = 500;
    private Environment environment;
    private SystemSetup sys;
    private int width, height, subjects;
    Random rand = new Random();
    private AbstractMatching matchingAlgo = new HungarianMatching();
    private ActionListener actionListener = new ActionListener() {

        public void onAction(String name, boolean pressed, float tpf) {
            System.out.println(renderManager.removeMainView("Top Left"));
        }
    };

    private PhysicsSpace getPhysicsSpace() {
        return bulletAppState.getPhysicsSpace();
    }

    @Override
    public void simpleInitApp() {
        bulletAppState = new BulletAppState();
        bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);
        stateManager.attach(bulletAppState);
        width = 1200;
        height = 1200;
        subjects = 6;
        bodies = new Node("Bodies");

        environment = new Environment(getPhysicsSpace(), bodies, this, width, height);
        sys = new SystemSetup(getPhysicsSpace(), cameras, rootNode, bodies, width, height);

        sys.AddDirectLight(ColorRGBA.White, Y);
        sys.AddDirectLight(ColorRGBA.White, Y.negate());
        /* Setup models */
        for (int i = 1, r = 300; i <= subjects; i++) {
            models.add(sys.addModel(ModelFactory.getModel(this, rand.nextInt())));
            models.get(i - 1).setLocalTranslation(
                    r * FastMath.cos(i * FastMath.TWO_PI / subjects),
                    160,
                    r * FastMath.sin(i * FastMath.TWO_PI / subjects));
        }
        environment.createObstacle(180, 150, 20, "Textures/chessboard.jpg");
        environment.createObstacle(180, 150, 20, "Textures/chessboard.jpg");
        environment.createObstacle(180, 150, 20, "Textures/chessboard.jpg");
        environment.createObstacle(180, 150, 20, "Textures/chessboard.jpg");

        for (int i = 1, r = 400; i < environment.getObstacles().size(); i++) {
            environment.getObstacles().get(i).setLocalTranslation(
                    r * FastMath.cos(i * FastMath.HALF_PI),
                    150,
                    r * FastMath.sin(i * FastMath.HALF_PI));
            environment.getObstacles().get(i).setLocalRotation(
                    new Quaternion().fromAngleNormalAxis((i - 1) * FastMath.HALF_PI, Y));
        }

        /* Setup input mapping */
        inputManager.addMapping("ToggleLights", new KeyTrigger(KeyInput.KEY_F));
        inputManager.addListener(actionListener, "ToggleLights");

        /* Setup cameras */

        // Setup master view
        cam.setLocation(new Vector3f(0f, 1800f, 0f));
        cam.lookAt(Y.negate(), Z);
        cam.setFrustumFar(2400f);
        renderManager.createMainView("main", cam.clone());
        cam.setViewPort(0f, 0.5f, 0f, 1f);



        // setting top rigth camera
        AbstractCamera cam1 = new SimCam(cam.clone());
        float viewPortValues1[] = {0.5f, 1f, 0f, 1f};
        Vector3f location1 = new Vector3f(-width / 2, cameraHeight, height / 2);
        Vector3f dir1 = new Vector3f(1f, 0f, 1f);
        cam1.setViewPort(viewPortValues1);
        sys.AddCamera(cam1, location1, dir1, "Top Right", renderManager, true, true);

    }
    int ccc = 0;
    int[] matching;
    Geometry[] vis = new Geometry[40];

    @Override
    public void simpleUpdate(float tpf) {
        for (BaseModel model : models) {
            model.move();
        }

        bulletAppState.update(tpf);

        // Camera update (PTZ) or re-match
        if (ccc++ % REFRESH_RATE > 0) {
            for (int i = 0; i < cameras.size(); i++) {
                if (matching[i] == -1) {
                    continue;
                }
                Vector3f modelPos = new Vector3f(models.get(matching[i]).getLocalTranslation());
                modelPos.setY(cameras.get(i).getLocation().getY());
                Vector3f dir = modelPos.subtract(cameras.get(i).getLocation());
                dir.setY(cameras.get(i).getLocation().getY());
                cameras.get(i).getCam().lookAt(dir, Y);
                float distance = dir.distance(cameras.get(i).getCam().getLocation());               
                if (distance < 1500) {
                    zoom(1,i);
                }
                else if (distance > 500) {
                    zoom(-1,i);
                }
                if (vis[i] != null) {
                    vis[i].removeFromParent();
                }

                vis[i] = makeLine("" + i, modelPos, cameras.get(i).getLocation(),
                        pick(cameras.get(i), models.get(matching[i])) ? ColorRGBA.Red : ColorRGBA.Blue);
                rootNode.attachChild(vis[i]);

            }
        } else {
            matching = matchingAlgo.match(buildGraph());
        }
    }

    private void zoom(float value, int i) {
        float h = cameras.get(i).getCam().getFrustumTop();
        float w = cameras.get(i).getCam().getFrustumRight();
        float aspect = w / h;

        float near = cameras.get(i).getCam().getFrustumNear();

        float fovY = FastMath.atan(h / near)
                / (FastMath.DEG_TO_RAD * .5f);
        fovY += value * 0.1f;

        h = FastMath.tan(fovY * FastMath.DEG_TO_RAD * .5f) * near;
        w = h * aspect;
        if ((fovY > 20 && value < 0) || (fovY < 50 && value > 0)) {
            cameras.get(i).getCam().setFrustumTop(h);
            cameras.get(i).getCam().setFrustumBottom(-h);
            cameras.get(i).getCam().setFrustumLeft(-w);
            cameras.get(i).getCam().setFrustumRight(w);
        }

    }

    public float[][] buildGraph() {
        boolean[][] picks = new boolean[cameras.size()][models.size()];
        float[][] g = new float[cameras.size()][models.size()];
        for (int i = 0; i < cameras.size(); i++) {
            for (int j = 0; j < models.size(); j++) {
                g[i][j] = (picks[i][j] = pick(cameras.get(i), models.get(j)))
                        ? cameras.get(i).getLocation().distance(models.get(j).getLocalTranslation()) : (1 << 30);
            }
        }
        return g;
    }

    public boolean pick(AbstractCamera cam, BaseModel model) {
        Ray ray = new Ray(cam.getLocation(), model.getLocalTranslation().subtract(cam.getLocation()));
        CollisionResults results = new CollisionResults();
        bodies.collideWith(ray, results);
        return results.size() > 0 && model.contains(results.getCollision(0).getGeometry());
    }

    public Geometry makeLine(String name, Vector3f s, Vector3f e, ColorRGBA c) {
        Line line = new Line(s, e);
        Geometry geom = new Geometry(name, line);
        Material mat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md");
        mat1.setColor("m_Color", c);
        geom.setMaterial(mat1);
        return geom;
    }

    @Override
    public void simpleRender(RenderManager rm) {
        //TODO: add render code
    }

    public static void main(String[] args) {
        SimulatorZ app = new SimulatorZ();
        app.start();
    }
}
